__metaclass__ = type

import re
from lex.token import *
from lex.tokenizer import Intnumber, Floatnumber
from grammy import exceptions
from nodes import Node
INT = re.compile(Intnumber)
FLOAT = re.compile(Floatnumber)
COMPOP = (EQUAL, LESS, GREATER, GREATEREQUAL, NOTEQUAL, LESSEQUAL)
ARITHOP = (PLUS, MINUS, STAR, SLASH, CIRCUMFLEX)
AND = 'and'
NOT = 'not'
OR = 'or'
LOGICOP = (AND, NOT, OR)
UNRESOLVED = object()

class SymTable(dict):

    def lookupType(self, name):
        return self[name][0]

    def lookupValue(self, name):
        return self[name][2]

    def defined(self, name):
        return self.has_key(name)

    def add(self, name, type, size, value=None):
        self[name] = (type, size, value)

    def remove(self, name):
        del self[name]

def generator(tokens):
    for t in tokens:
        yield t

class Machine:

    def __init__(self):
        self.sym = SymTable()
        self.temps = []
        self.labels = []
        self.t_counter = 0
        self.l_counter = 0
        self.enabled = True
        self.strict = True

    def defined(self, name):
        if not self.strict:
            return True
        return self.sym.defined(name)

    def addLabel(self):
        self.l_counter += 1
        name = 'L%s' % self.l_counter
        self.labels.append(name)
        return name

    def popLabel(self):
        self.l_counter -= 1
        return self.labels.pop()

    def addTemp(self, typ, value=None):
        self.t_counter += 1
        name = 'T%s' % self.t_counter
        if self.defined(name):
            raise exceptions.DuplicationError, (0, 0, name)
        self.sym.add(name, typ, value)
        self.temps.append(name)
        return name

    def lastTemp(self):
        return self.temps[-1]

    def popTemp(self):
        name = self.temps.pop()
        self.sym.remove(name)
        return name

    def reset(self):
        Machine.__init__(self)

    def atom(self, tokens, expr):
        if not self.enabled:
            return ()
        nodes = []
        for token_type, token, (srow, scol), (erow, ecol), line in tokens:
            typ = value = None
            if token_type == NAME:
                if not self.defined(token):
                    raise exceptions.NameError, (srow, scol, token)
                typ = self.sym.lookupType(token)
                value = self.sym.lookupType(token)
            elif token_type == NUMBER:
                if INT.match(token):
                    typ = int
                elif FLOAT.match(token):
                    typ = float
            elif token_type == COMMAND:
                if token in ('verdadeiro', 'falso'):
                    typ = bool
                if token == 'verdadeiro':
                    value = True
                elif token == 'falso':
                    value = False
            node = Node(token_type=token_type, token=token,
                        local=token, value=value, type=typ,
                        srow=srow, scol=scol, line=line)
            node.code.append(token)
            nodes.append(node)
        return nodes

    def promote(self, tokens, expr):
        if not self.enabled:
            return ()
        return expr.children

    def types(self, tokens, expr):
        if not self.enabled:
            return ()
        for token_type, token, (srow, scol), (erow, ecol), line in tokens:
            if token_type == COMMAND:
                # Finished declaration, add to symbol table
                if token == 'literal':
                    typ = str
                if token == 'inteiro':
                    typ = int
                if token == 'real':
                    typ = float
                if token == 'logico':
                    typ = bool
                node = Node(token_type=token_type, token=token,
                            srow=srow, scol=scol, line=line, type=typ)
                return (node,)

    def var_names(self, tokens, expr):
        if not self.enabled:
            return ()
        nodes = []
        for token_type, token, (srow, scol), (erow, ecol), line in tokens:
            if token_type == NAME:
                node = Node(token_type=token_type, token=token,
                            local=token, srow=srow, scol=scol, line=line)
                nodes.append(node)
        return nodes

    def variaveis(self, tokens, expr):
        if not self.enabled:
            return ()
        t = expr.children[-1]
        nodes = expr.children[:-1]
        for node in nodes:
            name = node.local
            if self.defined(name):
                raise exceptions.DuplicationError, (node.srow, node.scol, name)
            self.sym.add(name, t.type, None)

    def collect_children(self, tokens, expr):
        sub = []
        for node in expr.children:
            sub.extend(node.code)
        return sub

    def term(self, tokens, expr):
        if not self.enabled:
            return ()
        last_type = None
        last_op = None
        op_type = None
        next_type = None
        for node in expr.children:
            if op_type is not None and last_type is not None:
                if not (last_type in (int, float) and
                        node.type in (int, float)):
                    # If there's more than one term, both operands
                    # must be either int or float, because the
                    # operation is either 'STAR' or 'SLASH'.
                    raise exceptions.TypeError, (
                        node.srow, node.scol, node.token,
                        (last_type, node.type, last_op))
                else:
                    next_type = float in (node.type, last_type) and float or int
            if self.is_arg(node):
                last_type = node.type
            if node.token_type in (STAR, SLASH):
                last_op = node.token
                op_type = node.token_type

        if len(expr.children) > 1:
            typ = next_type or last_type
            token = self.addTemp(typ)
            node = Node(token_type=TEMP, token=token, type=typ, local=token,
                        srow=node.srow, scol=node.scol, line=node.line)
            sub_code = self.collect_children(tokens, expr)
            expr.children = [node]
            node.code = [token]
            expr.code.append('%s = %s' % (token, ' '.join(sub_code)))
        return ()

    def expr(self, tokens, expr):
        if not self.enabled:
            return ()
        last_type = None
        last_op = None
        op_type = None
        next_type = None
        for node in expr.children:
            if op_type is not None and last_type is not None:
                if (bool in (last_type, node.type) or
                    (str in (last_type, node.type) and not
                     last_type == node.type and
                     op_type in (PLUS,)) or
                    (last_type == node.type == str and not
                     op_type in (PLUS,))):
                    # Strings can only be combined with other strings
                    # *and* using PLUS
                    raise exceptions.TypeError, (
                        node.srow, node.scol, node.token,
                        (last_type, node.type, last_op))
                else:
                    if float in (node.type, last_type):
                        next_type = float
                    elif str in (node.type, last_type):
                        next_type = str
                    elif int in (node.type, last_type):
                        next_type = int
            if self.is_arg(node):
                last_type = node.type
            if node.token_type in (PLUS, MINUS):
                last_op = node.token
                op_type = node.token_type

        if len(expr.children) > 1:
            typ = next_type or last_type
            token = self.addTemp(typ)
            node = Node(token_type=TEMP, token=token, type=typ, local=token,
                        srow=node.srow, scol=node.scol, line=node.line)
            sub_code = self.collect_children(tokens, expr)
            expr.children = [node]
            node.code = [token]
            expr.code.append('%s = %s' % (token, ' '.join(sub_code)))
        return ()

    def comp(self, tokens, expr):
        if not self.enabled:
            return ()
        last_type = None
        last_op = None
        op_type = None
        next_type = None
        for node in expr.children:
            if op_type is not None and last_type is not None:
                if not (last_type == node.type or
                        bool in (last_type, node.type) or
                        (int in (last_type, node.type) and
                         float in (last_type, node.type))):
                    # Can only compare similar types or INT and FLOAT
                    raise exceptions.TypeError, (
                        node.srow, node.scol, node.token,
                        (last_type, node.type, last_op))
                else:
                    next_type = bool
            if self.is_arg(node):
                last_type = node.type
            if node.token_type in COMPOP:
                last_op = node.token
                op_type = node.token_type
        if len(expr.children) > 1:
            typ = next_type or last_type
            token = self.addTemp(typ)
            node = Node(token_type=TEMP, token=token, type=typ, local=token,
                        srow=node.srow, scol=node.scol, line=node.line)
            sub_code = self.collect_children(tokens, expr)
            expr.children = [node]
            node.code = [token]
            expr.code.append('%s = %s' % (token, ' '.join(sub_code)))
        return ()

    def test(self, tokens, expr):
        if not self.enabled:
            return ()
        last_type = None
        last_op = None
        op_type = None
        next_type = None
        for node in expr.children:
            if (op_type is not None and
                last_type is not None and
                self.is_arg(node)):
                if not last_type == node.type == bool:
                    # Can only test BOOL
                    raise exceptions.TypeError, (
                        node.srow, node.scol, node.token,
                        (last_type, node.type, last_op))
            if self.is_arg(node):
                last_type = node.type
            if node.token in LOGICOP:
                last_op = node.token
                op_type = node.token_type

        if len(expr.children) > 1:
            typ = bool
            token = self.addTemp(typ)
            node = Node(token_type=TEMP, token=token, type=typ, local=token,
                        srow=node.srow, scol=node.scol, line=node.line)
            sub_code = self.collect_children(tokens, expr)
            expr.children = [node]
            expr.code.append('%s = %s' % (token, ' '.join(sub_code)))
        node.code = expr.code
        return ()

    def is_arg(self, node):
        if (node.token_type in (NAME, NUMBER, STRING, TEMP) or
            (node.token_type == COMMAND and
             node.token in ('verdadeiro', 'falso'))):
            return True
        return False

    def io(self, tokens, expr):
        if not self.enabled:
            return ()
        token = 'IO'
        cmd = expr.children[0]
        if cmd.token == token:
            # Already processed
            return
        nodes = expr.children[1:]
        code = []
        for node in nodes:
            code.extend(node.code)
        expr.code.append('syscall(%s(%s))' % (cmd.token, ', '.join(code)))
        node = Node(token_type=STMT, token=token, local=token,
                    srow=cmd.srow, scol=cmd.scol, line=cmd.line)
        node.code = ['\n'.join(expr.code)]
        expr.children = [node]

    def assign(self, tokens, expr):
        if not expr.children:
            return
        if len(expr.children) == 1 and expr.children[0].token == 'ASSIGN':
            return
        target, op, node = expr.children
        if not target.type == node.type:
            raise exceptions.TypeError, (
                node.srow, node.scol, node.token,
                (target.type, node.type, op.token))
        expr.code.append('%s %s %s' % (target.token, op.token, node.token))
        token = 'ASSIGN'
        node = Node(token_type=STMT, token=token, local=token,
                    srow=target.srow, scol=target.scol, line=target.line)
        node.code = ['\n'.join(expr.code)]
        expr.children = [node]

    def stmt(self, tokens, expr):
        code = []
        token = 'STATEMENT'
        for node in expr.children:
            code.extend(node.code)
        node = Node(token_type=STMT, token=token, local=token,
                    srow=None, scol=None, line=None)
        node.code = ['\n'.join(code)]
        expr.children = [node]

    small_stmt = stmt

    def retrofix(self, code, label):
        code.reverse()
        for index, entry in enumerate(code):
            if entry is UNRESOLVED:
                code[index] = 'goto %s' % label
        code.reverse()

    def if_stmt(self, tokens, expr):
        if not self.enabled:
            return ()
        if not expr.children:
            return ()
        token = 'IF'
        if expr.children[0].token == token:
            # already processed
            return
        LABEL_END = self.addLabel()
        ELIF = False
        new_code = []
        gen = generator(expr.children)
        while True:
            node = gen.next()
            if node.token == 'senao':
                next = gen.next()
                if not next.token == 'se':
                    # else statement
                    stmt = next
                    next_label = self.addLabel()
                    new_code.append(
                        '%s: ' % next_label + ''.join(stmt.code)
                        )
                    self.retrofix(new_code, next_label)
                    continue
                else:
                    node = next
                    ELIF = True
            if node.token == 'se':
                test = gen.next()
                if not test.type == bool:
                    raise exceptions.TypeError, (
                        test.srow, test.scol, test.token,
                        (bool, test.type, token))
                if not ELIF:
                    then = gen.next()
                stmt = gen.next()
                prefix = ''
                if ELIF:
                    label = self.addLabel()
                    prefix = '%s: ' % label
                    self.retrofix(new_code, label)
                next_label = self.addLabel()
                if prefix:
                    new_code.append(prefix)
                new_code.extend(test.code)
                new_code.extend([
                    'if %s = 1 goto %s' % (test.token, next_label),
                    UNRESOLVED,
                    '%s: ' % next_label + ''.join(stmt.code),
                    'goto %s' % LABEL_END,
                    ])
                continue
            if node.token == 'fim_se':
                new_code.append('%s: ' % LABEL_END)
                self.retrofix(new_code, LABEL_END)
                break

        node = Node(token_type=STMT, token=token, local=token,
                    srow=None, scol=None, line=None)
        node.code = ['\n'.join(new_code)]
        expr.children = [node]

    def repeat_until_stmt(self, tokens, expr):
        if not self.enabled:
            return ()
        if not expr.children:
            return ()
        token = 'REPEAT_UNTIL'
        if expr.children[0].token == token:
            # already processed
            return
        LABEL_START = self.addLabel()
        LABEL_END = self.addLabel()
        new_code = []
        gen = generator(expr.children)
        node = gen.next()
        stmt = gen.next()
        until = gen.next()
        test = gen.next()
        if not test.type == bool:
            raise exceptions.TypeError, (
                test.srow, test.scol, test.token,
                (bool, test.type, token))
        new_code.extend([
            '%s: ' % LABEL_START + ''.join(stmt.code),
            ])
        new_code.extend(test.code)
        new_code.extend([
            'if %s = 1 goto %s' % (test.token, LABEL_END),
            'goto %s' % LABEL_START,
            '%s: ' % LABEL_END,
            ])

        node = Node(token_type=STMT, token=token, local=token,
                    srow=None, scol=None, line=None)
        node.code = ['\n'.join(new_code)]
        expr.children = [node]

    def while_stmt(self, tokens, expr):
        if not self.enabled:
            return ()
        if not expr.children:
            return ()
        token = 'WHILE'
        if expr.children[0].token == token:
            # already processed
            return
        LABEL_START = self.addLabel()
        LABEL_END = self.addLabel()
        new_code = []
        gen = generator(expr.children)
        node = gen.next()
        test = gen.next()
        if not test.type == bool:
            raise exceptions.TypeError, (
                test.srow, test.scol, test.token,
                (bool, test.type, token))
        next_label = self.addLabel()
        stmt = gen.next()
        new_code.append('%s:' % LABEL_START)
        new_code.extend(test.code)
        new_code.extend([
            'if %s = 1 goto %s' % (test.token, next_label),
            'goto %s' % LABEL_END,
            '%s: ' % next_label + ''.join(stmt.code),
            'goto %s' % LABEL_START,
            '%s: ' % LABEL_END,
            ])

        node = Node(token_type=STMT, token=token, local=token,
                    srow=None, scol=None, line=None)
        node.code = ['\n'.join(new_code)]
        expr.children = [node]

    def case_stmt(self, tokens, expr):
        if not self.enabled:
            return ()
        if not expr.children:
            return ()
        token = 'CASE'
        if expr.children[0].token == token:
            # already processed
            return
        LABEL_END = self.addLabel()
        new_code = []
        gen = generator(expr.children)
        case = gen.next()
        while True:
            node = gen.next()
            if node.token == 'outro_caso':
                stmt = gen.next()
                next_label = self.addLabel()
                new_code.append(
                    '%s: ' % next_label + ''.join(stmt.code)
                    )
                self.retrofix(new_code, next_label)
                continue
            if node.token == 'caso':
                test = gen.next()
                if not test.type == bool:
                    raise exceptions.TypeError, (
                        test.srow, test.scol, test.token,
                        (bool, test.type, token))
                stmt = gen.next()
                label = self.addLabel()
                self.retrofix(new_code, label)
                next_label = self.addLabel()
                new_code.append('%s:' % label)
                new_code.extend(test.code)
                new_code.extend([
                    'if %s = 1 goto %s' % (test.token, next_label),
                    UNRESOLVED,
                    '%s: ' % next_label + ''.join(stmt.code),
                    'goto %s' % LABEL_END,
                    ])
                continue
            if node.token == 'fim_caso':
                new_code.append('%s: ' % LABEL_END)
                self.retrofix(new_code, LABEL_END)
                break

        node = Node(token_type=STMT, token=token, local=token,
                    srow=None, scol=None, line=None)
        node.code = ['\n'.join(new_code)]
        expr.children = [node]

    def noop(self, tokens, expr):
        return ()

    def file_input(self, tokens, expr):
        code = []
        for n in expr.children:
            code.extend(n.code)
        expr.code = code
        return ()

machine = Machine()
